import java.awt.*;
import java.awt.event.*;
import java.util.HashMap;
import java.util.Iterator;

import javax.swing.*;
import javax.swing.border.LineBorder;
import javax.swing.border.TitledBorder;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

/**
 * RedBlackApp is responsible for for bootstrapping the red black tree 
 * application to the tabbed layout, rendering graphics and manipulating the 
 * redBlackTreee data structure - also holds all the GUI for (butons, sliders 
 * ect..) for the red black application
 * 
 * @see TwoFourApp
 * @author Simeon Gbolo, RedBlack sub Team
 * @modifier Jay Ripley
 * 
 */
public class RedBlackApp implements Application {

	// the screen for this application - See Class Below, it's a JPanel.
	public Screen screen;
	// the GUI for this application - See Class Below
	public TheGUI gui;
	// TODO: Change this object reference to the appropriate thing.
	public RedBlackTree<Integer> tree;

	// weather or not to calculate new locations
	public boolean dirty;;

	private static final int WORLD_WIDTH = 800;
	private static final int WORLD_HEIGHT = 500;

	// used to drag the tree across the screen
	static float rootX, rootY;
	static double scaleX, scaleY;

	/**
	 * used for the fixed-time-step simulation
	 */
	static final float TICK_DECREMENT = 0.05f;

	/**
	 * used for the fixed-time-step simulation and used to adjust the speed
	 */
	static float TICK_INITIAL = 0.001f;

	/**
	 * used for the fixed-time-step simulation and used to adjust the speed
	 */
	float tickTime = 0;

	public RedBlackApp()
	{
		gui = new TheGUI(this);
		screen = gui.getGraphicsScreen();
		// TODO: Replace this with the Relevant Tree Object.
		tree = new RedBlackTree<Integer>();
		dirty = false;
		rootX = WORLD_WIDTH / 2;
		rootY = 30;
		scaleX = 3;
		scaleY = 3;

	}

	public class RedBlackScreen extends Screen {


		public RedBlackScreen(Application app)
		{
			super(app);

		}

		public Dimension getPreferredSize()
		{
			return new Dimension(WORLD_WIDTH, WORLD_HEIGHT);
		}

		/**
		 * We tell the tree to update itself
		 */
		@Override
		public void update(float deltaTime)
		{
			tickTime += deltaTime;
			while (tickTime > TICK_INITIAL)
			{
				tickTime -= TICK_INITIAL;
				tree.update(deltaTime);
			}
		}

		@Override
		public void present(float deltaTime)
		{
			repaint();

		}

		@Override
		public void pause()
		{
			//not implemented

		}

		@Override
		public void paintComponent(Graphics g)
		{
			Graphics2D g2 = (Graphics2D) g;
			super.paintComponent(g2);
			// Make the drawings look more crisp
			g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
					RenderingHints.VALUE_ANTIALIAS_ON);
			g2.setFont(new Font(Font.SERIF, Font.BOLD, 14));

			// if a new node was added to the tree, recalculate the positions
			if (dirty)
			{
				tree.calculateNodeDestinations(g2.getFontMetrics());
				dirty = false;
			}
			// set the origin to the correct cordinate system
			g2.translate(rootX, rootY);
			g2.scale(scaleX, scaleY);
			// pass the graphics context to the tree so states can be drawn
			tree.drawStates(g2);

		}

	}

	/**
	 * All the GUI for the application
	 * 
	 * @author Simeon Gbolo
	 * @modifier Jay Ripley
	 * 
	 */
	class TheGUI extends JPanel implements ActionListener, MouseMotionListener,
			ChangeListener {

		// Canvas for Animation and Drawing
		private RedBlackScreen graphics;
		// Buttons
		private HashMap<String, JButton> buttons;
		// JPanels used for later rendering.
		JPanel textInfoPanel, inputPanel, compositePanel, sliderPanel;
		// Input Field - used by Event Listeners
		JTextField jtfInput;
		// Output Field - used by Event Listeners
		JTextArea jtaOutput;

		// used to adjust the size of the tree
		JSlider resizeSlider;

		// used to adjust the size of the tree
		JSlider animationSpeedSlider;

		public TheGUI(RedBlackApp red_black_anim)
		{
			graphics = new RedBlackScreen(red_black_anim);
			graphics.setBackground(Color.WHITE);
			textInfoPanel = new JPanel();
			sliderPanel = new JPanel();
			resizeSlider = new JSlider(1, 100, 100);
			animationSpeedSlider = new JSlider(1, 25, 15);
			resizeSlider.addChangeListener(this);
			animationSpeedSlider.addChangeListener(this);
			inputPanel = new JPanel();
			buttons = new HashMap<String, JButton>();

			jtfInput = new JTextField();
			jtfInput.setColumns(4);

			jtaOutput = new JTextArea(30, 10);
			jtaOutput.setEditable(false);
			jtaOutput.setLineWrap(true);
			jtaOutput.setWrapStyleWord(true);

			// Used as Structural Guidance
			compositePanel = new JPanel(new BorderLayout());

			createAndShowGUI();
		}

		/**
		 * Instantiate all the GUI objects
		 */
		private void createAndShowGUI()
		{
			/*
			 * Assemble the Button UI
			 */

			inputPanel.add(new JLabel("Value: "));
			inputPanel.add(jtfInput);

			// Insert Button
			buttons.put("Insert", new JButton("Insert Node"));
			// Find Button
			buttons.put("Find", new JButton("Find Node"));
			// Insert Button
			buttons.put("Delete", new JButton("Delete Node"));
			// Find Button
			buttons.put("Clear", new JButton("Clear Tree"));

			// Setup an action listener onto all of the newly created buttons.
			Iterator<String> keys = buttons.keySet().iterator();
			JButton current_button;

			while (keys.hasNext())
			{
				current_button = buttons.get(keys.next());
				current_button.addActionListener(this);
				inputPanel.add(current_button);
			}
			// add mouse listeners to graphics
			graphics.addMouseMotionListener(this);
			/*
			 * Assemble the Text Info UI
			 */
			LineBorder jtaBorder = new LineBorder(Color.BLACK, 1);
			TitledBorder jtaTitledBorder = new TitledBorder(jtaBorder,
					"Output: ");

			textInfoPanel.setBorder(jtaTitledBorder);
			textInfoPanel.add(jtaOutput);

			// set border for the slider
			LineBorder sliderBorder = new LineBorder(Color.BLACK, 1);
			TitledBorder sliderTitledBorder = new TitledBorder(sliderBorder,
					"Scale tree size");

			// set border for the slider
			LineBorder sliderBorder2 = new LineBorder(Color.BLACK, 1);
			TitledBorder sliderTitledBorder2 = new TitledBorder(sliderBorder2,
					"Adjust Animation Speed");

			resizeSlider.setBorder(sliderTitledBorder);
			animationSpeedSlider.setBorder(sliderTitledBorder2);
			sliderPanel.add(resizeSlider);
			sliderPanel.add(animationSpeedSlider);
			/*
			 * Assemble the Composite UI (Graphics and Text Field)
			 */
			compositePanel.add(graphics, BorderLayout.CENTER);
			compositePanel.add(textInfoPanel, BorderLayout.EAST);

			/*
			 * Assemble Parent UI.
			 */
			setLayout(new BorderLayout());
			add(sliderPanel, BorderLayout.NORTH);
			add(compositePanel, BorderLayout.CENTER);
			add(inputPanel, BorderLayout.SOUTH);
		}

		/**
		 * This action even method takes the appropriate action depending on
		 * what action was performed. Uses
		 * if(e.getsource().equals(someObjectWithListeners) The appropiate
		 * action will be taken.
		 */
		public void actionPerformed(ActionEvent e)
		{
			String error_log = "";
			String outPut = "";

			// Attempt to get Input Value, typically required.
			Integer returnInt = null;

			try
			{
				returnInt = Integer.parseInt(jtfInput.getText());
			} catch (NumberFormatException ne)
			{
				returnInt = null;
			}

			jtfInput.setText("");

			// Act based on source.
			if (e.getSource().equals(buttons.get("Insert")))
			{
				System.out.println("You hit Insert for RedBlack");

				if (returnInt != null)
				{
					if (returnInt >= 0 && returnInt <= 99)
					{
						// if the node already exist, give an error
						if (!tree.search(returnInt))
						{
							tree.insert(returnInt);
							// Commented out, Will onyl implement this feature
							// it becomes neccisary to show a node navigating
							// down
							// the tree
							// tree.setPath(returnInt);
							dirty = true;
						} else
						{
							error_log += "" + returnInt
									+ " Already Exist in the Tree";
						}
					} else
					{
						error_log = "Only numbers betwen 0-99 are acceptable";
					}

				} else
				{
					error_log = "Unable to parse input value. "
							+ " Please try again.";
				}
			} else
				if (e.getSource().equals(buttons.get("Find")))
				{
					//System.out.println("You hit Find for RedBlack.");

					if (returnInt != null)
					{
						// TODO: Tree Operations here.
						tree.search(returnInt);
					} else
					{
						error_log = "Unable to parse input value. "
								+ " Please try again.";
					}
				} else
					if (e.getSource().equals(buttons.get("Delete")))
					{
						//System.out.println("You hit delete for RedBlack.");

						if (returnInt != null)
						{
							//delete hack
							//tree.deleteReset(returnInt);
							// TODO: Tree Operations here.
							tree.delete((returnInt));
							dirty = true;
						} else
						{
							error_log = "Unable to parse input value. "
									+ " Please try again.";
						}
					} else
						if (e.getSource().equals(buttons.get("Clear")))
						{
							System.out.println("You hit clear for RedBlack.");
							tree.clear();
						}

			// Final Error Details
			if (error_log != "")
			{
				error_log += "\n---\n\n";
			}

			// Output Results
			jtaOutput.setText(error_log + tree.toString() + outPut);
		}

		public Screen getGraphicsScreen()
		{
			return this.graphics;
		}

		@Override
		public void mouseDragged(MouseEvent e)
		{
			// set the root of the tree to the x location of mouse
			rootX = e.getX();
			// set root of the tree to y location f the mouse
			rootY = e.getY();

		}

		@Override
		public void mouseMoved(MouseEvent e)
		{
			// TODO Auto-generated method stub

		}

		/**
		 * Used to adjust animation speed and scale the tree
		 */
		@Override
		public void stateChanged(ChangeEvent arg0)
		{
			double sliderVal = resizeSlider.getValue();
			scaleX = sliderVal * .03;
			scaleY = sliderVal * .03;

			int speed = animationSpeedSlider.getValue();
			TICK_INITIAL = ((float) speed * 2 * .0001f);

		}

	}

	@Override
	public TheGUI getTheGui()
	{
		return gui;
	}

	@Override
	public void setScreen(Screen screen)
	{
		this.screen = screen;

	}

	@Override
	public Screen getCurrentScreen()
	{
		return this.screen;
	}

}
